local base_prometheus = require("utils.prometheus.prometheus")
local core      = require("core")
local latency_details = require("utils.log-util").latency_details_in_ms
--local ipairs    = ipairs
--local pairs     = pairs
local ngx       = ngx
local re_gmatch = ngx.re.gmatch
--local pcall = pcall
--local select = select
--local type = type
local prometheus
local clear_tab = table.clear
--local unpack = unpack
--local next = next
--local ctx = ngx.ctx
local json = require("cjson.safe")


local ngx_capture
if ngx.config.subsystem == "http" then
    ngx_capture = ngx.location.capture
end


local plugin_name = "prometheus"
local prom_config =  require("conf.config").get("prometheus")

local log = core.log.new(plugin_name)
local default_export_uri = prom_config["export_uri"]
-- Default set of latency buckets, 1ms to 60s:
local DEFAULT_BUCKETS = {1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 30000, 60000}

local metrics = {}

local inner_tab_arr = {}

local function gen_arr(...)
    clear_tab(inner_tab_arr)
    --log.error(select('#', ...))

    for i = 1, select('#', ...) do
        --log.error(select(i, ...))
        inner_tab_arr[i] = select(i, ...)
    end

    return inner_tab_arr
end


local _M = {}



function _M.do_in_init()
    log.notice("do_in_init_worker","   ",ngx.get_phase())

    clear_tab(metrics)


    local metric_prefix = prom_config["metric_prefix"]


    prometheus = base_prometheus.init("prometheus_metrics", metric_prefix)

    metrics.connections = prometheus:gauge("nginx_http_current_connections",
            "Number of HTTP connections",
            {"state"})

    metrics.requests = prometheus:gauge("http_requests_total",
            "The total number of client requests since openresty started")

    --metrics.etcd_reachable = prometheus:gauge("etcd_reachable",
    --        "Config server etcd reachable from openresty, 0 is unreachable")


    metrics.node_info = prometheus:gauge("node_info",
            "Info of openresty node",
            {"hostname"})

    --metrics.etcd_modify_indexes = prometheus:gauge("etcd_modify_indexes",
    --        "Etcd modify index for APISIX keys",
    --        {"key"})

    metrics.shared_dict_capacity_bytes = prometheus:gauge("shared_dict_capacity_bytes",
            "The capacity of each nginx shared DICT since openresty start",
            {"name"})

    metrics.shared_dict_free_space_bytes = prometheus:gauge("shared_dict_free_space_bytes",
            "The free space of each nginx shared DICT since openresty start",
            {"name"})

    -- per service

    -- The consumer label indicates the name of consumer corresponds to the
    -- request to the route/service, it will be an empty string if there is
    -- no consumer in request.
    metrics.status = prometheus:counter("http_status",
            "HTTP status codes per service in openresty",
            {"code",  "app_name", "host"})

    metrics.limit = prometheus:counter("limit",
            "HTTP status codes per service in openresty",
            {"type","app_name", "host"})

    --metrics.latency = prometheus:histogram("http_latency",
    --        "HTTP request latency in milliseconds per service in openresty",
    --        {"type","host", "location", "node"},
    --        DEFAULT_BUCKETS)
    metrics.latency = prometheus:histogram("http_latency",
            "HTTP request latency in milliseconds per service in openresty",
            {"type","host", "app_name", "node"},
            DEFAULT_BUCKETS)

    --metrics.bandwidth = prometheus:counter("bandwidth",
    --        "Total bandwidth in bytes consumed per service in openresty",
    --        {"type","host", "location", "node"})

    metrics.bandwidth = prometheus:counter("bandwidth",
            "Total bandwidth in bytes consumed per service in openresty",
            {"type","host", "app_name", "node"})

end



function _M.do_in_log()
    local vars = ngx.var
    -- location多的话使用app_tmp，mexc在每个location里使用里配置 set $app_tmp "mxc-spot-order";
    --local route = vars.location or ""
    local route = vars.app_tmp or ""
    local service_id = vars.upstream_addr or ""

    local matched_host = vars.host or  ""
    -- "code",  "location", "host", "node"
    --log.notice("---------",vars.status, route,  matched_host ,vars.request_time,vars.upstream_response_time)
    metrics.status:inc(1,{vars.status,route, matched_host})
    if ngx.ctx.is_frequency_limit then
        metrics.limit:inc(1,gen_arr("http_limit_requests_total",route,matched_host))
        metrics.limit:inc(tonumber(ngx.var.request_length),gen_arr("http_limit_request_byte",route,matched_host))
    end


    metrics.latency:observe(tonumber(vars.request_time),
            gen_arr("request",matched_host,route, service_id))


    if vars.upstream_response_time then
        metrics.latency:observe(tonumber(vars.upstream_response_time),
                gen_arr("upstream",matched_host,route, service_id))
    end
    --
    ------ "host", "location", "node"
    metrics.bandwidth:inc(tonumber(vars.request_length),gen_arr("ingress",matched_host,route, service_id))

    metrics.bandwidth:inc(tonumber(vars.bytes_sent),{"egress",matched_host,route, service_id})
end




local ngx_status_items = {"active", "accepted", "handled", "total",
                          "reading", "writing", "waiting"}
local label_values = {}

local function nginx_status()
    local res = ngx_capture("/nginx_status")
    if not res or res.status ~= 200 then
        core.log.error("failed to fetch Nginx status")
        return
    end
    --log.notice("nginx_status 成功")

    -- Active connections: 2
    -- server accepts handled requests
    --   26 26 84
    -- Reading: 0 Writing: 1 Waiting: 1

    local iterator, err = re_gmatch(res.body, [[(\d+)]], "jmo")
    if not iterator then
        core.log.error("failed to re.gmatch Nginx status: ", err)
        return
    end

    for _, name in ipairs(ngx_status_items) do
        local val = iterator()
        if not val then
            break
        end

        if name == "total" then
            metrics.requests:set(val[0])
        else
            label_values[1] = name
            metrics.connections:set(val[0], label_values)
        end
    end
end



local function shared_dict_status()
    local name = {}
    for shared_dict_name, shared_dict in pairs(ngx.shared) do
        name[1] = shared_dict_name
        metrics.shared_dict_capacity_bytes:set(shared_dict:capacity(), name)
        metrics.shared_dict_free_space_bytes:set(shared_dict:free_space(), name)
    end
end


local function collect()
    if not prometheus  then
        core.log.error("prometheus: plugin is not initialized, please make sure ",
                " 'prometheus_metrics' shared dict is present in nginx template")
        return 500, {message = "An unexpected error occurred"}
    end

    -- collect ngx.shared.DICT status
    shared_dict_status()

    -- across all services
    nginx_status()

    -- config server status
    local vars = ngx.var or {}
    local hostname = vars.hostname or ""

    metrics.node_info:set(1, gen_arr(hostname))
    ngx.header.content_type = "text/plain"
    --core.response.set_header("content_type", "text/plain")
    return 200 , table.concat(prometheus:metric_data())
end
_M.collect = collect


local function get_api()
    local export_uri = default_export_uri
    local api = {
        methods = {"GET"},
        port = prom_config["port"],
        uri = export_uri,
        handler = collect,
        clear_methods = prom_config["clear_method"],
        clear_uri = prom_config["clear_uri"],
        allow_clear_ip = prom_config["allow_clear_ip"],

    }

    return api
end
_M.get_api = get_api


function _M.export_metrics()
    local api = get_api()
    local uri = ngx.var.uri
    local port = ngx.var.server_port
    local ip = ngx.var.remote_addr
    local method = ngx.req.get_method()
    if port == api.port then
        if uri == api.uri and method == api.methods[1]  then
            local code, body = api.handler()
            if code or body then
                if type(body) == "table" then
                    ngx.say(json.encode(body))
                else
                    ngx.say(body)
                end

                ngx.exit(code)
            end
        elseif uri == api.clear_uri and method == api.clear_methods and ip == api.allow_clear_ip  then
            ngx.shared.prometheus_metrics:flush_all()
            --ngx.shared.prometheus_metrics:flush_expired()
            ngx.say('{ok}')
            ngx.exit(200)
        else
            return ngx.exit(404)
        end
    end

end


function _M.metric_data()
    return prometheus:metric_data()
end

function _M.get_prometheus()
    return prometheus
end

return _M
